box::use(
# Packages
dplyr, ggplot2, stats,
dplyr[filter, select, mutate],
ggplot2[ggplot, geom_point],
stats[lm],
# Local modules
./mod, ./folder/mod,
./mod[fn1, ...],
./folder/mod[fn1, ...],
# Carrier packages
mod, mod/submodule,
mod[fn1, ...],
mod/submodule[fn1, ...]
)Array of Imports with Monikers
There are variety of import patterns to declare imports in box::use(). The specific pattern: mod[foo, bar, ...] allows specific qualification of imports, similar to from mod import foo, bar in Python, which uses indexing mechanism in R. Let’s first compare the traditional R approach to load/attach imports against {box}. Here’s the following table:
| Aspect | Base R | {box} Approach |
|---|---|---|
| Loading Method | library(dplyr) |
box::use(dplyr) / box::use(dplyr[...]) |
| Namespace Impact | Attaches to global namespace | Module-scoped imports |
| Conflict Handling | May cause naming conflicts | Explicit imports prevent conflicts |
| Code Clarity | Dependencies implicit | Dependencies explicitly declared |
| Performance | Loads entire package namespace | More controlled |
Oh, and there’s also require() function, an “evil-ler” twin of library() function, where it won’t just attach the entire namespace to the search path if the package exist, it returns in Boolean (TRUE if the package exists; FALSE if it doesn’t) when executed. There’s an inconsistency: it fails silently (“fails silently” means that a function fails without stopping the program or showing a clear error message).
Importing R Packages and scripts / folders
If the table is not enough to you, this section will further explain. Let’s explore the different importing methods:
Basic Imports
Chapter 1 already shows you the basic ways to import modules using box::use(). This function allows multiple imports at once, whether that module is a package, a local module, or packages managed by {carrier}. This is not different from the ellipsis (...) that allows passing multiple arguments, similar to Python’s *args and **kwargs. Differently, there’s a feature in box::use()’s bracketing method mod[] called “wildcard” in the form of ..., e.g. mod[...], which will attach all the names under that namespace.
Since this allows you to import multiple packages and their functions in a single call, separated by comma, you don’t have to bother yourself calling multiple calls, like:
When importing scripts and modules, you are going to provide the name of the path (should be a literal name, not a string), and/or add prefix ./ that indicates the current path. The use of ../ is allowed as well, but this will be discussed in Chapter 3.4. The rules applied on {box}-{carrier} modules except you are not allowed to place a prefix (./ or ../) since they are interpreted as packages, not local modules.
Importing the entirety
Do not confuse this with library(), where it attaches the entire namespace to the search path. Throwing all the exports into the search path is often subjectively (or objectively?) bad and is being discouraged in best software engineering practices. {box} has a different: This is the same as Python’s import math semantics, where instead of loading the package goes to the global environment, the namespace of the package and scripts / folders, including the functions and other objects, such as data frames or constants like pi from base R, are encapsulated as environments, (another data structure, similar to lists but mutable) with its name, and then the imports are accessed directly with $ subset operator.
box::use(
dplyr,
./mod1,
./mod2
)
dplyr$select(data, col1, col2)
mod1$fn1(mod2$fn3(5))How beautiful this may be? The entire package namespace becomes available as an environment. All functions are (and must) be accessed using the package name as a prefix and $ operator. This is nice as it prevents namespace pollution while maintaining access to all functions. This might not sound useful since you already did that with pkg:: in base R, this is still useful, especially for local modules and {box}-{carrier} modules, where you don’t want to allow to qualify their names.
The use of aliases also allowed:
box::use(
dpr = dplyr,
tdr = tidyr,
md1 = ./module1,
md2 = ./module2
)
# Usage:
dpr$filter(data, col1 > 0) |>
tdr$pivot_longer(cols = col2)
md1$fn1(md2$fn3(5))Granular Imports with Aliases {spec-imports}
{box} absolves namespace clashes, which commonly occur when different packages have functions with the same name.
As you import the names, you are allowed to spice up things a little bit by renaming the original names during the imports. This is superbly useful when you have 2 packages to be used and you want to use them both at once. For instance, the {dplyr}‘s filter() function, and as you attach the {dplyr} namespace, it will mask the existing functions from the global namespace, namely the {stats}’ filter() function. This induces namespace clash, and there are times you may not want this happening.
Fortunately with {box}, you can load the filter() function from the {dplyr} namespace through the following:
box::use(
dplyr[keep_when = filter], # dplyr's `filter()` becomes `keep_when()`
stats[filter_ts = filter] # stats' filter becomes filter_ts
)
data |> keep_when(col > 0)
AirPassengers |> filter_ts() But wait, there’s another thing you need to know. There’s a gotcha when you declare an import under box::use(), i.e. something like box::use(pkg = package[fn1]), two things will happen: this imports pkg carrying the entire package namespace, and at the same time will attach fn1 names under package namespace.
Granular imports in R’s library() also allowed using include.only parameter.
For example:
library(dplyr, include.only = c("select", "filter"))But still no alias gimmicks and doesn’t even leverage non-standard evaluation, where it treats include.only arguments as an object, called as a name, unlike box::use().
R version 4.4 and above has a shorthand of library(pkg, include.only = c('fn1', 'fn2')): Introducing the base::use().
Example usage:
use(dplyr, c("select", "filter"))This is inconsistent, as well, and generally not recommendable..
Importing non-syntactic names
The infix operators like %>% from {magrittr} are considered non-syntactic names. In R, operators and functions that use special characters (like +, *, %>%, %in%, %*%, etc.) are called infix operators but internally parsed as the usual function call in R, e.g. `+`(a, b). These operators require special handling when importing with {box}. Regardless, yes, these are still functions.
To import functions with special characters, you need to wrap them in backticks:
box::use(
magrittr[`%>%`, `%T>%`],
dplyr[group_by, summarise, n, mutate],
stats[sd, median]
)
iris %>%
group_by(Species) %>%
summarise(
mu = mean(Sepal.Length, na.rm = TRUE),
sigma = sd(Sepal.Length, na.rm = TRUE),
md = median(Sepal.Length, na.rm = TRUE)
) %T>%
print() %>%
mutate(cv = sigma / mu, .before = md)# A tibble: 3 × 4
Species mu sigma md
<fct> <dbl> <dbl> <dbl>
1 setosa 5.01 0.352 5
2 versicolor 5.94 0.516 5.9
3 virginica 6.59 0.636 6.5
# A tibble: 3 × 5
Species mu sigma cv md
<fct> <dbl> <dbl> <dbl> <dbl>
1 setosa 5.01 0.352 0.0704 5
2 versicolor 5.94 0.516 0.0870 5.9
3 virginica 6.59 0.636 0.0965 6.5
Wildcard import
The true equivalent of library(pkg) is through box::use(pkg[...]). Yet again, ... are called “ellipsis”. The use of ... sets as a “wildcard”, and this, while being granular, imports all the namespace within the package (or modules). The Python’s equivalent would be from pkg import *.
For example:
box::use(
dplyr[...]
)Imports within the function / function call
When we import packages / scripts / folders as modules or import their namespace, did you know the imports are enclosed within the scope?
According to the official documentation:
the effects of
box::use()are restricted to the current scope: we can load and attach names inside a function, and this will not affect the calling scope (or elsewhere).
Here’s an example:
Code
mtcars |>
dplyr::reframe(
{
box::use(
stats[linear_reg = lm, pearson_r = cor],
purrr[imap_dfc, set_names],
tibble[tbl = tibble]
)
model = linear_reg(mpg ~ wt)
coefs = coef(model)
coef_table = imap_dfc(coefs, \(bi, nm) {
result = tbl(bi)
set_names(result, nm)
})
corr = pearson_r(wt, mpg)
test = summary(model)
tbl(
coef_table,
corr = corr,
rsq = test$r.squared,
adj_rsq = test$adj.r.squared
)
},
.by = cyl
)
#> cyl (Intercept) wt corr rsq adj_rsq
#> 1 6 28.40884 -2.780106 -0.6815498 0.4645102 0.3574122
#> 2 4 39.57120 -5.647025 -0.7131848 0.5086326 0.4540362
#> 3 8 23.86803 -2.192438 -0.6503580 0.4229655 0.3748793This code is made to study the type-I error by examining how true the linear relationship between wt and mpg variables from mtcars data, when performing statistical analysis. Here, this code imports within dplyr::reframe() function call without making side-effect the current environment. This is great if you create a function with external dependencies available from R packages, or within your scripts / folders.
Best Practices for Package Imports
But, of course, R packages have strengths, but don’t forget their flaws. I will enumerate the do’s when using {box} package:
Be Specific with Imports
Please, do not import everything, unless there’s a good use case, like when you ALL of them. The use of wildcards, i.e. the “ellipsis”
...within the indexing syntax through[...]is a shortcut to import the namespace, but you are importing everything here.box::use( dplyr[...] )Don’t just do this in actual practice, or it will create a mess in the global namespace, just like
library(). As the Zen of Python said: “Explicit is better than implicit.”Instead, import only what you need. Of course, in several times, you only import specific parts of the package only. For instance, when you are aggregating data frame with
{dplyr}, you often only needsfilter(),select(),mutate(),group_by(), andsummarise(). Mind you that there are a total of 293 exported namespaces (will be less than that if you don’t count the pseudo-functions, such asacross()andwhere()) within{dplyr}package, and for your aggregation task, you only need 5 out of the total exports.This approach is better because it is explicit and you can even rename those imports:
box::use( dplyr[filter_df = filter, select, mutate, group_by, summarise] )Group Related Imports
This is just an opinion. For better clarity, you can explain the imports you are making within the function call
box::use()with comments#. This is not unusual, sometimes this is common. Since comments are allowed, you can group the imports by functionality.box::use( # Simple aggregation dplyr[filter_df = filter, select, mutate, group_by, summarise], tidyr[pivot_longer, pivot_wider], # To run linear regression and t-test stats[linear_reg = lm, welch_ttest = t.test], # To visualize outputs from linear regression and t-test ggplot2[ggplot, geom_point, geom_smooth, geom_box, theme_minimal, aes] )Handle Naming Conflicts
As discussed here, you are allowed to place an alias within the imports, so that the namespace clash will be resolved.
The most prominent example is
dplyr::filter()andstats::filter().box::use( dplyr[keep_when = filter], stats[filter_ts = filter] )Rename them whatever you want to employ clarity.
Troubleshooting Package Imports
Frankly, I will show you solutions if you have similar errors like the following. The common issues to be found when importing packages happen are:
Obviously, when the R package is not installed. This matter is trivial: The package does not exist in your current environment, and simply just install the packages you need and import them using
box::use().box::use(pkg[func, ...]) #> Error in box::use(pkg) : there is no package called ‘pkg’ install.packages("pkg")When the particular imports does not exist in the package namespace or incorrectly name the import:
box::use(dplyr[nonexistent_function]) #> Error in box::use(dplyr[nonexistent_function, slct]) : name “nonexistent_function”, “slct” not exported by “dplyr” box::use(dplyr[select, filter]){box}enforces strict naming. If possible, check out the official documentation of the R package. Check if you are using indeed correct spelled name, and check if the imports does exist in the package namespace